package com.morty.config.annotation;

import com.alibaba.fastjson.JSONObject;
import com.morty.base.enums.SharedEnums;
import com.morty.config.model.TableModifyLog;
import com.morty.config.utils.ThreadContextHolder;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.ui.ModelMap;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * @author
 * @title: TableModifyLogAspect
 * @description:
 * @date 2020/8/21 16:05
 */
@Slf4j
@Aspect
@Order(1)
@Component
public class TableModifyLogAspect {

    @Value("${spring.application.name}")
    private String applicationName;

    @Autowired
    RabbitTemplate rabbitTemplate;

    @Pointcut("execution (* com.morty.*.controller..*(..)) || execution (* com.morty.*.*.controller..*(..))")
    public void tableModifyCut() {
    }

    @Around("tableModifyCut()")
    public Object logSave(ProceedingJoinPoint joinPoint) throws Throwable {
        //PassAspect注解不需要保存业务日志
        if (joinPoint.getTarget().getClass().isAnnotationPresent(PassAspect.class)) {
            return joinPoint.proceed();
        }

        long startMilli = System.currentTimeMillis();
        // 被请求方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        String methodName = method.getName();

        String rootPath = null;
        String uri = null;
        String userDevice = null;
        String userIp = null;
        String entryMethod = null;

        // request信息
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes != null) {
            HttpServletRequest request = attributes.getRequest();
            // 请求方式
            entryMethod = request.getMethod();
            // 如果是GET请求，不切
//            if (entryMethod.equalsIgnoreCase(HttpMethod.GET.name())) {
//                return joinPoint.proceed();
//            }
            // 设置IP地址
            userIp = getIPAddress(request);
            // 设置根路径
            String path = request.getContextPath();
            rootPath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path;
            // 获取uri
            uri = request.getRequestURI();
            // 获取用户设备信息
            userDevice = request.getHeader("User-Agent");
        }

        // 如果是POST请求，但是方法/路径包含查询类字样，不切
        if (entryMethod.equalsIgnoreCase(HttpMethod.POST.name()) && isQueryRequest(methodName, uri)) {
            return joinPoint.proceed();
        }

        // 方法上的api说明内容
        ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
        String apiOperationName = "";
        if(apiOperation != null) {
            apiOperationName = apiOperation.value();
        }else{
            apiOperationName = method.getName();
        }
        Api api = method.getDeclaringClass().getAnnotation(Api.class);
        String apiName = "";
        if(api != null) {
            apiName = api.value();
        }else{
            apiName = method.getDeclaringClass().getName();
        }
        String operationDesc = apiName + "-" + apiOperationName;

                // 请求参数
        String params = null;
        try {
            List<Object> requestParams = getHttpReqArgs(joinPoint);
            if (requestParams.size() == 1) {
                params = JSONObject.toJSONString(requestParams.get(0));
            } else if (requestParams.size() > 1) {
                params = JSONObject.toJSONString(requestParams);
            }
        } catch (Exception e) {
            log.error("request params parsing failed");
        }
        // 方法的返回结果
        Object result = null;
        Integer requestResult = null;
        try {
            // 调用原来的方法
            result = joinPoint.proceed();
            requestResult = SharedEnums.DoingStatus.Success.getKey();
        } catch (Exception e) {
            requestResult = SharedEnums.DoingStatus.Fail.getKey();
            throw e;
        } finally {
            long finishMilli = System.currentTimeMillis();
            // 登录用户
            String operator = "";
            try {
                //TODO 获取用户名
                operator = "userName";
            } catch (Exception e) {
                log.error(e.getMessage());
            }
            //构建业务日志主体对象
            TableModifyLog tableModifyLog = TableModifyLog.builder()
                    .operationDesc(operationDesc)
                    .methodName(methodName)
                    .className(method.getDeclaringClass().getName())
                    .userDevice(userDevice)
                    .entryMethod(entryMethod)
                    .uri(uri)
                    .rootPath(rootPath)
                    .userIp(userIp)
                    .params(params)
                    .logId(ThreadContextHolder.getTraceId())
                    .module(applicationName)
                    .operator(operator)
                    .operationTime(new Date())
                    .costMilliTime(finishMilli - startMilli)
                    .requestResult(requestResult)
                    .build();
            //发送消息
            try {
                rabbitTemplate.convertAndSend("BusinessLogExchange", "BusinessLog."+applicationName, tableModifyLog);
                log.info("BusinessLog发送消息成功");
            } catch (Exception e) {
                log.error(e.getMessage());
            }
        }
        return result;
    }

    //获取入参
    private List<Object> getHttpReqArgs(ProceedingJoinPoint joinPoint) {
        List<Object> httpReqArgs = new ArrayList<Object>();
        for (Object object : joinPoint.getArgs()) {
            if (!(object instanceof HttpServletRequest) && !(object instanceof HttpServletResponse)
                    && !(object instanceof ModelMap) && !(object instanceof MultipartFile)) {
                httpReqArgs.add(object);
            }
        }
        return httpReqArgs;
    }

    //过去IP地址
    private String getIPAddress(HttpServletRequest request) {
        String ip = null;
        //X-Forwarded-For：Squid 服务代理
        String ipAddresses = request.getHeader("X-Forwarded-For");

        if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
            //Proxy-Client-IP：apache 服务代理
            ipAddresses = request.getHeader("Proxy-Client-IP");
        }
        if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
            //WL-Proxy-Client-IP：weblogic 服务代理
            ipAddresses = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
            //HTTP_CLIENT_IP：有些代理服务器
            ipAddresses = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
            //X-Real-IP：nginx服务代理
            ipAddresses = request.getHeader("X-Real-IP");
        }
        //有些网络通过多层代理，那么获取到的ip就会有多个，一般都是通过逗号（,）分割开来，并且第一个ip为客户端的真实IP
        if (ipAddresses != null && ipAddresses.length() != 0) {
            ip = ipAddresses.split(",")[0];
        }
        //还是不能获取到，最后再通过request.getRemoteAddr();获取
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }


    //判断是否是查询请求
    private boolean isQueryRequest(String methodName, String uri){
        String methodNameLower = methodName.toLowerCase();
        if (methodNameLower.startsWith("get") || methodNameLower.startsWith("select") || methodNameLower.startsWith("query") ||
                methodNameLower.startsWith("page") || methodNameLower.startsWith("find")) {
            return true;
        }
        if (methodNameLower.endsWith("get") || methodNameLower.endsWith("select") || methodNameLower.endsWith("query") ||
                methodNameLower.endsWith("page") || methodNameLower.endsWith("find")) {
            return true;
        }
        String entryUriLower = uri.toLowerCase();
        if (entryUriLower.indexOf("get") >= 0 || entryUriLower.indexOf("select") >= 0 || entryUriLower.indexOf("query") >= 0 ||
                entryUriLower.indexOf("page") >= 0 || entryUriLower.indexOf("find") >= 0) {
            return true;
        }
        return false;
    }
}
